Skip to content

feat(spec)!: 应用外壳与导航树拒绝未知键(#4001 app 步 PR B) - #4165

Merged
os-zhuang merged 2 commits into
mainfrom
claude/unknown-key-stripping-strictness-h3a3zt
Jul 30, 2026
Merged

feat(spec)!: 应用外壳与导航树拒绝未知键(#4001 app 步 PR B)#4165
os-zhuang merged 2 commits into
mainfrom
claude/unknown-key-stripping-strictness-h3a3zt

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

收掉未知键棘轮里最后一个高流量可授权面(前序:#4071 flow+permission、#4099 rls/sharing/position、#4119 approval、#4142 app 死键墓碑)。应用外壳是全平台手写密度最高的面——导航树正是作者(和 AI)最容易凭记忆写键的地方——所以这里的静默剥离是 #3405 陷阱最可能的发生地。

做了什么

  • AppSchema 及其全部子 schema .strict():branding、navigation area、context selector(含 optionsSource / filter 嵌套块)、navigation contribution。
  • NavigationItemSchema 转为按 type 判别的 union——这正是让 strict 可读的关键。实测对比:
场景 普通 union + strict 判别 union
匹配分支上的未知键 invalid_union 聚合墙,列出全部 9 个分支的失败 单条 unrecognized_keys,只报作者真正写的那个分支
嵌套 children 里的未知键 路径丢失在聚合里 精确到 navigation.0.children.0
type 拼错 同一堵墙 专属的 "Invalid discriminator value"

每个变体还带自己的建议池——url 项永远不会被建议 dashboardName。仓内先例:view.zod.tswidget.zod.ts

  • 有意保持开放PageNavItem.params / ComponentNavItem.params / ActionNavItem.actionDef.params——这些是页面/组件/动作各自的载荷契约,不归导航项管。

闸门抓到的第 6 条 finding —— 这次在平台自己的元数据里

ACCOUNT_APP 在三个导航组上写了 defaultOpen schema 的键是 expandeddefaultOpen 从来不是——所以这三个组一直是折叠着发布的,而作者以为它们默认展开。已按 contract-first(Prime Directive #12)修产出方,并把 defaultOpen/open/collapsed/isOpen 加进别名表指向 expanded

值得注意这条的发现位置:不在租户项目里,而在已经发布了若干个版本的第一方平台元数据里。这是对「仓内是干净的」这个推断的第三次证伪。

迁移

被拒的键此前全被剥离、从未生效,删除或改名行为保持。错误自带修复方案,映射包括:menu/sidebar/tabs/itemsnavigationtitlelabelpermissionsrequiredPermissionssort/positionorderdefaultOpenexpandedargsparams(actionDef)、primaryprimaryColorurlendpoint(选项源);错层指引:pages/views/flows 不是 App 字段,写错变体的载荷会被指向拥有它的 type

最要紧的一条别名是 visibleWhenvisible——和 #3746 在动作参数上发现的是同一个坑:ADR-0089 让 visibleWhen 成为 view/page 的正统拼法,在导航项上借用它过去会把整个可见性门剥掉、让条目无条件渲染。能力门 fail-open 是静默剥离最坏的形态。

零破坏实证

  • spec 272 文件 / 7103 用例全过,tsc 干净
  • 12 个 check:* 闸门全过
  • platform-objects 239 全过(Setup / Studio / Account 三个平台应用,共 ~80 个导航项)
  • dogfood 73 文件 / 425 用例全过
  • 三示例 validate 全过(showcase 含 58 个导航项

战役状态

至此四大域核心授权面全部覆盖:数据(object/field)、UI(action/view/page/app)、自动化(flow/approval)、安全(permission/rls/sharing/position)。剩余按账本:data/hook + datasource(小、暂定分类待验证)→ v17 后的 lint 告警层(非破坏,minor 可发)→ v18 用告警期实证数据收长尾。

Refs #4001

🤖 Generated with Claude Code

https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4


Generated by Claude Code

…tree (#4001 app step, PR B)

Closes the last high-traffic authorable surface in the unknown-key strictness
ratchet. The app shell is the densest hand-authored surface on the platform —
a navigation tree is where an author or AI is most likely to write a key from
memory — so a silent strip here was the most probable instance of the #3405
trap.

- AppSchema and its sub-schemas (branding, navigation area, context selector
  + its optionsSource/filter blocks, navigation contribution) are .strict().
- NavigationItemSchema becomes a DISCRIMINATED union on `type`. That is what
  makes strict readable: a plain union of strict members answers one unknown
  key with an invalid_union aggregate naming all nine branches, while
  discriminating on `type` first yields a single unrecognized_keys issue
  against the branch the author actually wrote — at an exact path through
  nested children — and a mistyped `type` gets its own "Invalid discriminator
  value". Each variant carries its own suggestion pool, so a `url` item is
  never told about `dashboardName`.
- Still OPEN by design: PageNavItem.params, ComponentNavItem.params and
  ActionNavItem.actionDef.params — per-target payloads owned by the page /
  component / action, not by the nav item.

Sixth ledger finding, and the first in first-party platform metadata:
ACCOUNT_APP declared `defaultOpen` on three navigation groups. That was never
a schema key — `expanded` is — so all three shipped COLLAPSED while their
author believed they opened by default. Fixed at the producer (contract-first,
Prime Directive #12); defaultOpen/open/collapsed/isOpen now alias to
`expanded` so the next author is pointed at it.

Verified: spec 7103 tests + tsc clean; all 12 check gates; platform-objects
239 (setup/studio/account apps); dogfood 73 files / 425 tests; showcase (58
nav items) / crm / todo validate clean.

Refs #4001

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 30, 2026 1:36pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ui tooling labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/platform-objects, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/platform-objects, @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…tor`

The app shell doc claimed eight navigation item types and enumerated eight,
omitting `separator` — the schema has declared nine since the inverse-drift
fix (#1878/#1891/#1894) that added it to match the objectui renderer's
`item.type === 'separator'` branch.

The gap matters more now that this PR discriminates the union on `type`: a
mistyped `type` reports the valid discriminator list, so the doc's enumeration
is the thing authors check it against. And `SeparatorNavItemSchema` is
`.strict()` over only `type`/`id`/`order` — it does not spread
`BaseNavItemSchema` — so `label`/`icon`/`badge`/`visible`/`requiredPermissions`
on a separator are now hard errors where they used to be silently stripped.
The "all navigation items share these base properties" table was the exact
sentence that would send an author into that error, so it now names the
exception.

Both claims asserted against the live schema before writing: the union has
nine members, and a separator accepts type/id/order while rejecting each of
the five base props.

Refs #4001

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
@os-zhuang
os-zhuang marked this pull request as ready for review July 30, 2026 13:48
@os-zhuang
os-zhuang merged commit 09e4547 into main Jul 30, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/unknown-key-stripping-strictness-h3a3zt branch July 30, 2026 13:48
os-zhuang pushed a commit that referenced this pull request Jul 30, 2026
…ge gate did its job

Merge origin/main and record the first live graduation: #4165 made the app
shell strict while this PR was in CI. The derivation adapted on its own — app
fell out of the lintable set because its parse is now loud — and the pinned
expectation list is what turned that shrink into a red test instead of a silent
one. That is the exact scenario the pin exists for: a human confirms the shrink
is a graduation, not a regression, and moves the name from the covered list to
the strict list.

Changeset and docs updated to stop hardcoding the count and the strict roster —
both now describe the rule (the strict bucket grows as tiers graduate) rather
than a snapshot of it.

7133 tests green on the merged base; all six gates PASS; the three example apps
stay clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXGj3Z5TmwSV6RK2oGc3cb
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…ection (#3786) (#4178)

#4148's lint covered object and field — a sample, not the population: only a
handful of authorable types are .strict(); every other type strips an
undeclared key with no signal. The walker now covers every lintable collection
(16 today), with its coverage DERIVED, not listed: collections from
PLURAL_TO_SINGULAR, the judging schema from the type→Zod registry, and
lintability read off each schema's own unknown-key posture (strip → lint;
strict → skip, the parse is loud; passthrough → skip, nothing is dropped;
unions → member-key union).

The walker lives in kernel/ beside the schema registry; the comparator and
guidance tables stay in data/, frontend-safe. Root export unchanged, so
defineStack, os validate and os build pick the coverage up with no change.

Mid-review, #4165 graduated `app` to strict — the derivation adapted on its
own, and the pinned coverage expectation turned the shrink into a red test
until a human confirmed it was a graduation, not a regression. That is the
gate working as designed; the strict roster in the test now records it.

Verified clean against the three example apps (zero false positives), by
mutation in three directions, and 7133 spec tests green on the merged base.
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…itten doc (#4177)

The liveness gate asks whether a property does anything; check-doc-authoring asks
whether docs use the right authoring form. Neither asked the inverse-drift question:
does a variant the schema DECLARES appear in the prose at all? content/docs/references/
is generated from these schemas and cannot drift; the hand-written pages are typed by
humans and do.

Founding case, which shipped for months: apps.mdx said the navigation tree "supports
eight item types" and enumerated eight. The schema had nine — `separator` was added to
match the objectui renderer (#1878/#1891/#1894) and no hand-written page ever learned
about it. #4165 raised the cost: a mistyped discriminator now answers with the list of
valid values, and the doc's enumeration is what an author checks it against.

Identity is `<discriminator>:<variants sorted>`, deliberately not a source path — a union
is reachable by several paths and the walk order picks one. Keying on the variant set
means adding or removing a variant changes the key, failing the ratchet and sending the
author back through the ledger to the doc.

variant-docs.json classifies all 20 unions found by reflection (a count that matches
`grep -c 'z.discriminatedUnion('` exactly): 5 governed, 15 exempt as either
generated-reference-only or not-authorable.

Verified it bites, not just that it passes: removing `separator` from apps.mdx fails with
exit 1 naming the variant; deleting a ledger entry fails as an undeclared union; mutating
a key fails as orphan + undeclared — the pair an author sees when adding a variant.

Known limit, stated in the script header: a variant counts as documented via
`<disc>: '<variant>'` or `` `<variant>` ``. The second form is loose and can match an
unrelated sentence for short generic variants — a pass means the page says the word, not
that it documents it correctly.

Refs #4001, #4165
os-zhuang pushed a commit that referenced this pull request Jul 30, 2026
Two conflicts, both in code #4165 ("reject unknown keys across the app shell
and navigation tree") rewrote underneath this branch:

- `packages/spec/src/ui/app.zod.ts` — main turned the nav union into a
  `z.discriminatedUnion('type', …)` with `.strict()` members and kept the
  `z.ZodType<any>` annotation; this branch replaced that annotation with the
  real `NavigationItem`. Both are kept. Reconciling them needed one addition:
  main widens the member array (`as unknown as readonly [ZodObject<ZodRawShape>,
  …]`) because the members are lazySchema Proxies and a superRefine-wrapped
  variant, and `discriminatedUnion` then reports the union's output as
  `Record<string, unknown>` — which fits `z.ZodType<any>` and nothing sharper.
  The union therefore takes a matching cast back to `z.ZodType<NavigationItem>`,
  documented at the declaration: every branch of the type is still
  `z.infer<typeof XNavItemSchema>`, so only the MEMBERSHIP of the list is
  unchecked, and app.test.ts parses all nine variants.

- `AGENTS.md` — main added the `check:generated` aggregate section; this branch
  added `check:exported-any` to the "pure checks with no generator" list. Both
  are kept.

Also registers `check:exported-any` in `check-generated.ts`'s ledger, which
main added and which reconciles itself against package.json on every run.

Registers `check:variant-docs` in the same ledger while here: #4177 added the
script and #4183 added the reconciliation, neither PR could see the other, so
`check:generated` was already failing on `main` itself. One line, in a file this
branch was editing anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XY3swmCsdYx6AMGiRmFt1H
os-zhuang added a commit that referenced this pull request Jul 31, 2026
…#4232)

The ledger is the campaign's map — which z.object sites are authorable (the
.strict() ratchet target), which are wire, which are deliberately open. Every
step reads it to pick the next move, and nothing kept it honest. It went stale
twice in one week: a classification verification disproved (hook.zod.ts carried
a blanket `authorable (p)` covering a runtime shape, #4207), and a "next step"
that had shipped months earlier (the unknown-key warning layer, #4218), which
sent the next reader off to rebuild something already in production.

A map that drifts is worse than no map, because it gets followed.

CHECKS. Site counts (the ledger states its own method — "z.object( occurrences
per file" — so every number is verifiable); coverage (every *.zod.ts with sites
in a triaged directory needs a row); section totals; and that a row claiming
"strict as of" names a file that really contains .strict().

The Class column is deliberately NOT checked. Authorable vs wire vs open is a
judgement about who writes the input, and this campaign's rule is
verify-before-tightening — encoding it would automate exactly the call that must
stay human. The gate protects the arithmetic and coverage so that judgement is
always made against current code.

Zero-site files are skipped: five of the six the first run flagged hold no
z.object at all (enum/token modules). Not a hole — the day one grows its first
z.object( it becomes undeclared and the gate says so.

FIRST RUN: 11 DRIFTS, in a file being actively edited by the campaign that owns
it. Six counts had moved (ui/app.zod.ts 11 → 18, ui/touch.zod.ts 4 → 7,
ui/action.zod.ts 8 → 9, ui/view.zod.ts 51 → 50, ui/responsive.zod.ts 6 → 4,
automation/flow.zod.ts 12 → 11), two section totals no longer summed, and
automation/io-node-config.zod.ts had no row. The app.zod.ts gap was
self-inflicted: the app step (#4165) added seven schemas and updated the row's
prose without touching its count.

Then it worked for real before merging: main landed
automation/builtin-node-config.zod.ts (#4045/#4228, eight sites), and merging
main turned the gate red on a branch whose own diff touches no schema. Now
classified. Every pre-existing count survived that merge unchanged, so the
failure was exactly as narrow as it should have been.

Verified the gate bites rather than just passes: a moved count fails (plus its
section total), a deleted row resurfaces as an undeclared file, and stripping
.strict() from approval.zod.ts fails its "strict as of" claim.

Refs #4001
os-zhuang added a commit that referenced this pull request Jul 31, 2026
…checked (#4244)

Follow-up to the #4177 variant/doc gate, using the signal it produced. That gate
passes a variant if the page contains either `<disc>: '<variant>'` or a bare
`` `variant` ``, and its header says plainly that the second form is loose: a
pass means the page says the word, not that it documents the thing. Six variants
were passing on the loose form alone — the gate's own "weak pass" list, and a
precise worklist.

apps.mdx enumerated nine navigation item types but only documented six. `report`,
`action` and `component` shared one sentence — "the spec also defines …" — while
the other six each had a section with an example. They now have their own, and
the intro no longer implies a second tier of half-supported types.

The `action` section earns its place: `actionName` lives inside a nested
`actionDef` block, not at the top level, which is exactly the shape a reader
guesses wrong. Both it and `component` note which sub-object stays OPEN by design
(`actionDef.params`, `component.params`) — the sibling contract owns those, and
after #4165 everything around them is strict.

knowledge.mdx had a table naming `object` / `file` / `http` and no example of
any, so the per-kind keys (`contentFields` vs `prefix` vs `urls`) appeared
nowhere. Added one example each.

Every example was parsed against the real schema before committing —
NavigationItemSchema and KnowledgeSourceKindSchema, six for six. Writing docs
from memory is how the region example in #4218 ended up using an `id` key that
does not exist.

Anchored-form coverage for these two unions: 9/12 → 12/12.

Carries an empty changeset. The first push asserted a docs-only PR needs none;
Check Changeset disagreed and named the fix — an empty changeset is the repo's
way for work that releases nothing, and 72 of them already exist.

Refs #4001, #4177
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants